/*
 * Copyright (c) 2011-2012 Alexander Dubu
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * o  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * o  Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * o  Neither the name Axil nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package axil.framework.config;

import axil.etc.NameValue;
import axil.framework.resource.ResourceManager;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;

import static axil.framework.Functions.*;


/**
 * A configuration file mechanism with a bit more smarts and a bit of type
 * safety.
 */
public class Configuration {
	private static final Configuration instance = load("data/axil.cfg");
	private static final class Value {
		private String raw;
		private Object value;

		Value(String raw) {
			this.raw = dequote(raw);
		}

		void set(Object value)	{
			this.value = value;
		}
		String raw()			{
			return raw;
		}
		Object get()			{
			return value;
		}
	}

	private String filename;
	private Map<String,Value> map;


	public Configuration(File file) {
		this.filename = file.getAbsolutePath();
		try {
			load(new FileReader(file));
		} catch (FileNotFoundException e) {
			throw abort(e);
		}
	}


	public Configuration(String name) {
		this(name, ResourceManager.instance().reader(name));
	}


	public Configuration(String filename, Reader in) {
		this.filename = filename;
		load(in);
	}


	private void load(Reader in) {
		this.map = new HashMap<String, Value>();
		Properties props = new Properties();
		try {
			props.load(in);
		} catch (IOException e) {
			throw abort(e);
		} finally {
			close(in);
		}
		for (Entry entry : props.entrySet()) {
			map.put(entry.getKey().toString(),
			        new Value(entry.getValue().toString()));
		}
	}


	public static final Configuration axil() {
		return instance;
	}


	/**
	 * Load an Axil configuration file. The resource loaders configured by the
	 * AXIL environment variable are used, in order.
	 */
	public static Configuration load(String name) {
		return new Configuration(name, ResourceManager.instance().reader(name));
	}


	private Value entry(String key) {
		Value e = map.get(key);
		if (e == null) {
			throw abort(
			    "The configuration file does not have an entry with that key.",
			    nv("file", filename), nv("key", key));
		}
		return e;
	}


	public String text(String key) {
		return entry(key).raw();
	}


	public String text(String key, String def) {
		Value e = map.get(key);
		return e == null ? def : e.raw();
	}


	public int integer(String key) {
		Value v = entry(key);
		Integer i = (Integer)v.get();
		if (i == null) {
			try {
				i = Integer.parseInt(v.raw());
				v.set(i);
			} catch (NumberFormatException e) {
				throw abort("The configuration value is not an integer.",
				            nv("file", filename), nv("key", key));
			}
		}
		return i;
	}

	public List<String> list(String key) {
		Value v = entry(key);
		List<String> c = (List<String>)v.get();
		if (c == null) {
			c = new ArrayList<String>();
			for (String s : split(v.raw())) {
				c.add(s.trim());
			}
			v.set(c);
		}
		return c;
	}


	public List<String> list(String key, boolean required) {
		Value e = map.get(key);
		if (e == null) {
			return Collections.emptyList();
		}
		List<String> c = (List<String>)e.get();
		if (c == null) {
			c = new ArrayList<String>();
			for (String s : split(e.raw())) {
				c.add(s.trim());
			}
			e.set(c);
		}
		return c;
	}


	/**
	 * This method returns a list of zero or more name/value pairs by scanning
	 * the configuration for values with the indicated prefix. For example, if
	 * the string "foo-" were specified, then the name/value pairs for "foo-bar"
	 * and "foo-baz" would be returned. The prefix is automatically stripped
	 * off, so the returned keys would be "bar" and "baz" in this example.
	 */
	public List<NameValue> values(String prefix) {
		List<NameValue> list = new ArrayList<NameValue>();
		for (String k : map.keySet()) {
			if (k.startsWith(prefix)) {
				list.add(nv(deprefix(k, prefix), map.get(k).raw()));
			}
		}
		return list;
	}
}
